A Stateful session bean can optionally use an extended context. An extended context maintains its managed objects between transactions or even in situation where a method is not using transactions. All objects accessed or created hang around until the bean goes away. This normally happens when a client executes a method that has been denoted as a Remove method (annotated with @Remove or declared as such in XML).
This short tutorial demonstrates some of the differences between these two types of container-managed contexts.
For the remainder of this tutorial, when you see <project>, replace it with Ejb3Tutorial4.
Create the Project
Pull down the File menu and select new:project
Select Java Project
Click Next
Enter <project>
Under Project Layout select create separate source and output folders
Click Finish
Select <project>, right-click and select new:Source Folder
Enter conf for the name
Click on Finish
Select <project>, right-click and select new:Source Folder
Enter test for the name
Click on Finish
Edit your project properties
Now that we have created a user library, we can add that user library to our project:
Select <project>, and press alt-enter or right-click and select properties.
Select Java Build Path
Select the Libraries tab
Click on Add Library
Select User Library and click Next
Click on the box next to EJB3_EMBEDDABLE and click Finish
Click Add Library
Select JUnit and click Next
In the pull-down list, select JUnit 4 and click Finish
Click on OK to make the change to your project's classpath
Setup the configuration files
The JBoss Embeddable container looks for several files in the classpath. To keep all of these in one place, we'll add another source directory to our project and then import several files into that directory.
Select the conf folder under <project>
Pull down the File menu and select Import
Expand General
Select File System and click on Next
Click on Browse and go to the following directory: C:/libs/jboss-EJB-3.0_Embeddable_ALPHA_9/conf
Click on OK
You'll see conf in the left pane, select it
Verify that the Into folder: lists <project>/conf (if not, just enter it or browse to it)
Click Finish
Expand the conf directory and verify that the files are now there
Add Resource Adapter Archive(RAR)
The Java Connector system defines Resource Adapter Archive files (RAR files). We need to add a few RAR files into the class path. We will import two more files into the conf directory:
Select the conf folder
Pull down the File menu and select Import
Expand General
Select File System and click on Next
Click on Browse and go to the following directory: C:/libs/jboss-EJB-3.0_Embeddable_ALPHA_9/lib
Select jcainflow.rar and jms-ra.rar
Click Finish
Create a jndi.properties file
Note, depending on the version of the embeddable container you download, you might already have a file called jndi.properties. If you do, skip to the next section.
Select the conf directory, right-click and select new then select File
Enter the name jndi.properties and click finish
Enter the following 2 lines then save and close the file:
This example presents a utility class we'll be using later. The container needs a persistence.xml file to operate. This file must be found under a META-INF directory somewhere in the classpath or the embeddable container will not start. The file's name is persistence.xml with a lower-case 'p'. On a Unix system, this will make a difference. On a PC, this won't make a difference and it is one of those things that might work on your machine but not on the linux build box.
Select your src directory
Right-click, select New:Folder
Enter META-INF
Click OK
Select META-INF
Right-lick, select New:File
Enter persistence.xml
Click Finish
Copy the following example into your new file then save it by pressing ctrl-s
persistence.xml
<?xmlversion="1.0"encoding="UTF-8"?><persistence><persistence-unitname="custdb"><!-- This persistence unit uses the default data source that JBoss --><!-- defines called DefaultDS. If we wanted to use our own data --><!-- source, we'd need to define a custom data source somewhere. --><!-- That somewhere is vendor specific. --><!-- In the case of JBoss, since we're using the embedded container, --><!-- we need to add two beans in a file called --><!-- embedded-jboss-beans.xml. We name the first --><!-- HypersonicLocalServerDSBootstrap and we name the second --><!-- HypersonicLocalServerDS. This two step process defines a data --><!-- source. --><!-- In the first bean definition, we additionally bind it in Jndi --><!-- under some name. If we used the name --><!-- java:/HypersonicLocalServerDS then we would use the following --><!-- entry to use that data source instead of the default one: --><!-- <jta-data-source>java:/HypersonicLocalServerDS</jta-data-source> --><jta-data-source>java:/DefaultDS</jta-data-source><properties><propertyname="hibernate.hbm2ddl.auto"value="create-drop"/></properties></persistence-unit></persistence>
Here are a few things to note (source for all of these items appears at the end after the assignments EJB3 Tutorial 4 - Extended Context:
Make sure you copy the utils directory from a previous tutorial.
Make sure you copy a persistence.xml from a previous tutorial.
Make sure you update the persistence.xml's persistence-unit name:
<persistence-unitname="tolltag">
The Entity Model
For this example, we have a simple entity model. We have an Account that has a bidirectional one-to-many relationship with TollTag objects and a bidirectional one-to-many relationship with Vehicle objects. Normally, one-to-many relationships are lazily fetched. For this example, the relationship with TollTag objects is left as lazily fetched while the relationship with Vehicle objects is eagerly fetched.
Account.java
packageentity;importjava.util.ArrayList;importjava.util.Collection;importjavax.persistence.CascadeType;importjavax.persistence.Entity;importjavax.persistence.FetchType;importjavax.persistence.GeneratedValue;importjavax.persistence.Id;importjavax.persistence.OneToMany;/**
* This is a simple Account class that knows about toll tags and vehicles. An
* account has a one to many relationship with both toll tags and vehicles. By
* default, one to many relationships are lazily loaded. To demonstrate
* differences between extended scope contexts and transaction-scoped contexts,
* one of these relationships is eagerly fetched.
*/
@Entitypublicclass Account {
@Id
@GeneratedValue
privateLong id;/**
* This relationship is lazily fetched. This means a client using a detached
* Account will not be able to access tollTags unless the relationship was
* touched while the object was still managed.
*/
@OneToMany(mappedBy = "account", cascade = CascadeType.ALL)privateCollection<TollTag> tollTags;/**
* We eagerly fetch this relationship to show that doing so allows this
* relationship to work if the object is or is not detached. NOTE: only
* one "Collection" can have a fetch property of EAGER. If you want to
* to use fetch = FetchType.EAGER more than once in the same class, the
* other "Collections" will have to be "Set"s.
*/
@OneToMany(mappedBy = "account", cascade = CascadeType.ALL,
fetch = FetchType.EAGER)privateCollection<Vehicle> vehicles;public Account(){}publicLong getId(){return id;}publicCollection<TollTag> getTollTags(){if(tollTags == null){
tollTags = newArrayList<TollTag>();}return tollTags;}publicCollection<Vehicle> getVehicles(){if(vehicles == null){
vehicles = newArrayList<Vehicle>();}return vehicles;}publicvoid addVehicle(final Vehicle vehicle){
getVehicles().add(vehicle);
vehicle.setAccount(this);}publicvoid removeVehicle(final Vehicle vehicle){
getVehicles().remove(vehicle);
vehicle.setAccount(null);}publicvoid addTollTag(final TollTag tollTag){
getTollTags().add(tollTag);
tollTag.setAccount(this);}publicvoid removeTollTag(final TollTag tt){
getTollTags().remove(tt);
tt.setAccount(null);}}
TollTag.java
packageentity;importjavax.persistence.Column;importjavax.persistence.Entity;importjavax.persistence.GeneratedValue;importjavax.persistence.Id;importjavax.persistence.ManyToOne;importjavax.persistence.NamedQueries;importjavax.persistence.NamedQuery;importutil.EqualsUtil;/**
* For some hard to decipher results, change the "FROM TollTag t" to "FROM
* TollTag".
*/
@Entity
@NamedQueries({
@NamedQuery(name = "TollTag.associatedAccount",
query = "SELECT t.account FROM TollTag t WHERE tagNumber = :tagNumber"),
@NamedQuery(name = "TollTag.byTolltagNumber",
query = "SELECT t FROM TollTag t WHERE tagNumber = :tagNumber")})publicclass TollTag {
@Id
@GeneratedValue
privateLong id;
@Column(unique = true)privateString tagNumber;
@ManyToOne
private Account account;publicLong getId(){return id;}publicvoid setId(finalLong id){this.id = id;}publicString getTagNumber(){return tagNumber;}publicvoid setTagNumber(finalString tagNumber){this.tagNumber = tagNumber;}public Account getAccount(){return account;}publicvoid setAccount(final Account account){this.account = account;}
@Overridepublicboolean equals(finalObject rhs){return rhs instanceof TollTag
&& EqualsUtil.equals(getTagNumber(), ((TollTag) rhs)
.getTagNumber());}
@Overridepublicint hashCode(){return getTagNumber().hashCode();}}
packagesession;importjavax.ejb.Local;importentity.Account;/**
* This interface is a bit abnormal as it is being used for both a stateful and
* stateless session bean. See individual method comments for clarification.
*/
@Local
publicinterface AccountInventory {void removeTag(finalString tagNumber);
Account findAccountByTagNumber(finalString tagNumber);/**
* Strictly speaking, this method is required only for transaction-managed
* contexts. If you use a stateful session bean with an extended context,
* then changed to any managed objects will eventually be written. There's
* no need to actually call an update method.
*
* If you have a stateless session bean or a stateful session bean using a
* transaction-scoped context, then you need to call an update method after
* making changes to an object outside of a bean because the object is no
* longer managed.
*/
Account updateAccount(final Account account);/**
* When do updates happen to objects managed by an extended-context manager?
* Answer, when the client calls a so-called remove method (annotated with
* -at- Remove or denoted so in an XML file).
*
* This method serves no purpose for a stateless session bean. For a
* stateful session bean using an extended context, when the client calls
* this method, the container knows it is time to write all of the changes
* it has been tracking to the database.
*
*/void finish();
Account findAccountById(finalLong id);void removeAccount(final Account account);void createAccount(final Account account);}
This class performs the same two test algorithms two times each for a total of 4 test methods:
Name
Scope
Accesses
Expected
createExampleUsingVehiclesTransactionScoped
Transaction
Vehicles
Success
createExampleUsingVehiclesExtendedScoped
Extended
Vehicles
Success
createExampleUsingTollTagsTransactionScoped
Transaction
TollTags
Fails
createExampleUsingTollTagsExtendedScoped
Extended
TollTags
Success
AccountInventoryBeanTest.java
packagesession;importstaticorg.junit.Assert.assertEquals;importorg.junit.BeforeClass;importorg.junit.Test;importutil.JBossUtil;importentity.Account;importentity.TollTag;importentity.Vehicle;publicclass AccountInventoryBeanTest {
@BeforeClass
publicstaticvoid initContainer(){
JBossUtil.startDeployer();}publicstatic AccountInventory getInventory(){return JBossUtil.lookup(AccountInventory.class,
"AccountInventoryBean/local");}publicstatic AccountInventory getExtendedInventory(){return JBossUtil.lookup(AccountInventory.class,
"AccountInventoryExtendedBean/local");}publicstatic TollTag instantiateTollTag(){final TollTag tt = new TollTag();
tt.setTagNumber("1234567890");return tt;}publicstatic Vehicle instantiateVehicle(){returnnew Vehicle("Subaru", "Outback", "2001", "YBU 155");}/**
* This method creates an account, looks it up and then accesses the toll
* tags relationship. The toll tags relationship is lazily loaded. If the
* passed-in bean is one that uses a transaction-managed context, then the
* assert will fail because the relationship has not been initialized.
*
* On the other hand, if the bean is one that uses an extended persistence
* context, then the assert will pass because the relationship, will still
* lazily loaded, will get initialized when accessed since the account
* object is still managed.
*/privatevoid createExampleTestTollTagsImpl(final AccountInventory bean){final Account account = new Account();
account.addTollTag(instantiateTollTag());
account.addVehicle(instantiateVehicle());
bean.createAccount(account);try{final Account found = bean.findAccountById(account.getId());
assertEquals(1, found.getTollTags().size());}finally{
bean.finish();
getInventory().removeAccount(account);}}/**
* As a counter example to createExampleTestTollTagsImpl, this method
* follows the same step but instead uses the vehicles relationship. Since
* this relationship has been set to fetch eagerly, it is available
* regardless of whether or not the account object is still managed.
*/privatevoid createExampleTestVehiclesImpl(final AccountInventory bean){final Account account = new Account();
account.addTollTag(instantiateTollTag());
account.addVehicle(instantiateVehicle());
bean.createAccount(account);try{final Account found = bean.findAccountById(account.getId());
assertEquals(1, found.getVehicles().size());}finally{
bean.finish();
getInventory().removeAccount(account);}}
@Test
publicvoid createExampleUsingVehiclesTransactionScoped(){
createExampleTestVehiclesImpl(getInventory());}
@Test
publicvoid createExampleUsingVehiclesExtendedScoped(){
createExampleTestVehiclesImpl(getExtendedInventory());}
@Test
publicvoid createExampleUsingTollTagsTransactionScoped(){
createExampleTestTollTagsImpl(getInventory());}
@Test
publicvoid createExampleUsingTollTagsExtendedScoped(){
createExampleTestTollTagsImpl(getExtendedInventory());}}
Exercises
Test Passing
There are 3 ways to make the one failing test pass:
Touch the collection before returning from the method
Change the query from using getEm().find(...) to instead use a named query such as "SELECT a FROM Account a JOIN FETCH a.tollTags WHERE a.id = :id"
Add fetch = FetchType.EAGER to the attribute, but note that if you do, you might need to change the type from Collection<TollTag> to Set<TollTag> due to a limitation in implementation of the entity manager.
Experiment with each of these options.
Shared Code
There is a lot of shared code between the two AccountInventory bean implementations. Describe at least two ways you could reduce the redundancy.
Interfaces
The interface seems to be a bit messed up with concepts that relate to both stateless and stateful beans. Describe how you might change the interface to make this better. Consider using two interfaces instead of one.
packageutil;importjava.io.ByteArrayOutputStream;importjava.io.IOException;importjava.io.OutputStream;importjava.io.PrintStream;importjava.util.logging.Logger;importjavax.naming.InitialContext;importjavax.naming.NamingException;importorg.jboss.ejb3.embedded.EJB3StandaloneBootstrap;/**
* This class was originally necessary when using the ALPHA 5 version of the
* embeddable container. With the alpha 9 release, initialization is quite
* simple, you need just 2 lines to initialize your JBoss Embeddable EJB3
* Container Environment. Unfortunately, the one that is available for download
* uses System.out.println() in a few places, so this simple utility hides that
* output and also provides a simple lookup mechanism.
*/publicclass JBossUtil {privatestaticPrintStream originalOut;privatestaticPrintStream originalErr;privatestaticOutputStream testOutputStream;privatestaticPrintStream testOuputPrintStream;staticboolean initialized;staticInitialContext ctx;private JBossUtil(){// I'm a utility class, do not instantiate me}/**
* JBoss EJB3 Embeddable Container uses System.out. Redirect that output to
* keep the console output clean.
*/privatestaticvoid redirectStreams(){// configure the console to get rid of hard-coded System.out's in// the JBoss libraries
testOutputStream = newByteArrayOutputStream(2048);
testOuputPrintStream = newPrintStream(testOutputStream);
originalOut = System.out;
originalErr = System.err;System.setOut(testOuputPrintStream);System.setErr(testOuputPrintStream);}/**
* Restore the System.out and System.err streams to their original state.
* Close the temporary stream created for the purpose of redirecting I/O
* while the initializing is going on.
*/privatestaticvoid restoreStreams(){System.setOut(originalOut);System.setErr(originalErr);
testOuputPrintStream.close();try{
testOutputStream.close();}catch(IOException e){Logger.getLogger(JBossUtil.class.getName()).info("Unable to close testoutstream");}}/**
* This method starts and initializes the embeddable container. We do not
* offer a method to properly clean up the container since this is really
* meant for testing only.
*
* This method may freely be called as often as you'd like since it lazily
* initializes the container only once.
*/publicstaticvoid startDeployer(){if(!initialized){
redirectStreams();
EJB3StandaloneBootstrap.boot(null);
EJB3StandaloneBootstrap.scanClasspath();
initialized = true;
restoreStreams();}}/**
* This is for symmetry. Given how we are using this class, there's little
* need to actually shutdown the container since we run a quick application
* and then stop the JVM.
*/publicstaticvoid shutdownDeployer(){
EJB3StandaloneBootstrap.shutdown();}privatestaticInitialContext getContext(){/**
* We only keep one context around, so lazily initialize it
*/if(ctx == null){try{
ctx = newInitialContext();}catch(NamingException e){thrownewRuntimeException("Unable to get initial context", e);}}return ctx;}/**
* The lookup method on InitialContext returns Object. This simple wrapper
* asks first for the expected type and the for the name to find. It gets
* the name out of JNDI and performs a simple type-check. It then casts to
* the type provided as the first parameter.
*
* This isn't strictly correct since the cast uses the expression (T), where
* T is the generic parameter and the type is erased at run-time. However,
* since we first perform a type check, we know this cast is safe. The -at-
* SuppressWarnings lets the Java Compiler know that we think we know what
* we are doing.
*
* @param <T>
* Type type provided as the first parameter
* @param clazz
* The type to cast to upon return
* @param name
* The name to find in Jndi, e.g. XxxDao/local or, XxxDao/Remote
* @return Something out of Jndi cast to the type provided as the first
* parameter.
*/
@SuppressWarnings("unchecked")publicstatic<T> T lookup(Class<T> clazz, String name){finalInitialContext ctx = getContext();/**
* Perform the lookup, verify that it is type-compatible with clazz and
* cast the return type (using the erased type because that's all we
* have) so the client does not need to perform the cast.
*/try{finalObject object = ctx.lookup(name);if(clazz.isAssignableFrom(object.getClass())){return(T) object;}else{thrownewRuntimeException(String.format("Class found: %s cannot be assigned to type: %s",
object.getClass(), clazz));}}catch(NamingException e){thrownewRuntimeException(String.format("Unable to find ejb for %s", clazz.getName()), e);}}}
EqualsUtil.java
packageutil;/**
* We typically need to compare two object and also perform null checking. This
* class provides a simple wrapper to accomplish doing so.
*/publicclass EqualsUtil {private EqualsUtil(){// I'm a utility class, do not instantiate me}publicstaticboolean equals(finalObject lhs, finalObject rhs){return lhs == null&& rhs == null
|| (lhs != null&& rhs != null&& lhs.equals(rhs));}}
Table of Contents
Ejb3 Tutorial 4 - Extended Context
A Stateful session bean can optionally use an extended context. An extended context maintains its managed objects between transactions or even in situation where a method is not using transactions. All objects accessed or created hang around until the bean goes away. This normally happens when a client executes a method that has been denoted as a Remove method (annotated with @Remove or declared as such in XML).This short tutorial demonstrates some of the differences between these two types of container-managed contexts.
Project Setup
The instructions for setting up your project mirror those from the first tutorial: EJB3 Tutorial 1 - Create and Configure.For the remainder of this tutorial, when you see <project>, replace it with Ejb3Tutorial4.
Create the Project
Edit your project properties
Now that we have created a user library, we can add that user library to our project:Setup the configuration files
The JBoss Embeddable container looks for several files in the classpath. To keep all of these in one place, we'll add another source directory to our project and then import several files into that directory.Add Resource Adapter Archive(RAR)
The Java Connector system defines Resource Adapter Archive files (RAR files). We need to add a few RAR files into the class path. We will import two more files into the conf directory:Create a jndi.properties file
Note, depending on the version of the embeddable container you download, you might already have a file called jndi.properties. If you do, skip to the next section.Create a persistence.xml
This example presents a utility class we'll be using later. The container needs a persistence.xml file to operate. This file must be found under a META-INF directory somewhere in the classpath or the embeddable container will not start. The file's name is persistence.xml with a lower-case 'p'. On a Unix system, this will make a difference. On a PC, this won't make a difference and it is one of those things that might work on your machine but not on the linux build box.persistence.xml
Here are a few things to note (source for all of these items appears at the end after the assignments EJB3 Tutorial 4 - Extended Context:
<persistence-unit name="tolltag">The Entity Model
For this example, we have a simple entity model. We have an Account that has a bidirectional one-to-many relationship with TollTag objects and a bidirectional one-to-many relationship with Vehicle objects. Normally, one-to-many relationships are lazily fetched. For this example, the relationship with TollTag objects is left as lazily fetched while the relationship with Vehicle objects is eagerly fetched.
Account.java
TollTag.java
Vehicle.java
The Session Beans
AccountInventory.java
AccountInventoryBean.java
AccountInventoryExtendedBean.java
The Tests
This class performs the same two test algorithms two times each for a total of 4 test methods:
AccountInventoryBeanTest.java
Exercises
Test Passing
There are 3 ways to make the one failing test pass:Experiment with each of these options.
Shared Code
There is a lot of shared code between the two AccountInventory bean implementations. Describe at least two ways you could reduce the redundancy.Interfaces
The interface seems to be a bit messed up with concepts that relate to both stateless and stateful beans. Describe how you might change the interface to make this better. Consider using two interfaces instead of one.Other Files
persistence.xmlJBossUtil.java
EqualsUtil.java
<--Back